package org.adl.registry.handles;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Properties;
import net.handle.api.HSAdapter;
import net.handle.api.HSAdapterFactory;
import net.handle.hdllib.Common;
import net.handle.hdllib.Encoder;
import net.handle.hdllib.HandleException;
import net.handle.hdllib.HandleValue;


/**
 * This utility provides methods for interacting with the Handle System. It provides simplified functionality to the Handle System api developed by CNRI tailored to the needs of the ADL Registry website.
 * @author Jacob Marks - Joint ADL Co-Lab
 * @version 1.0
 * @license This software is licensed as open source software pursuant to the terms defined in the Joint ADL Co-Lab Open Source Software License.  Use of any part of this software constitutes acceptance of this license. The Joint ADL Co-Lab open source agreement is available at {@link http://adlregistry.adlnet.gov/source_license.html}.
 */
public class HandleUtil
{
    /**
     * The index of an ADL Registry user HS_SECKEY attribute within a user handle.
     */
    private static int CONTRIBUTOR_PRIVATE_KEY_INDEX = 301;
    
    /**
     * Creates a new Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iValues The set of values to be added to the new Handle.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle creation.
     */
    public static void createHandle(String iHandle, HandleValue[] iValues)
    throws FileNotFoundException, IOException, HandleException
    {
        HSAdapter api = HandleUtil.getHandleApi();
        
        HandleUtil.createHandle(iHandle, iValues, api);
    }
    
    /**
     * Creates a new Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iValues The set of values to be added to the new Handle.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle creation.
     */
    public static void createHandle(String iHandle, HandleValue[] iValues, HSAdapter iApi)
    throws HandleException
    {
        iApi.createHandle(iHandle, iValues);
    }
    
    /**
     * Deletes a Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle deletion.
     */
    public static void deleteHandle(String iHandle)
    throws FileNotFoundException, IOException, HandleException
    {
        HSAdapter api = HandleUtil.getHandleApi();
        
        HandleUtil.deleteHandle(iHandle, api);
    }
    
    /**
     * Deletes a Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle deletion.
     */
    public static void deleteHandle(String iHandle, HSAdapter iApi)
    throws HandleException
    {
        iApi.deleteHandle(iHandle);
    }
    
    /**
     * Updates a single <CODE>HandleValue</CODE>.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iValue The <CODE>HandleValue</CODE> to be used to replace the current HandleValue target.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle update.
     */
    public static void updateHandleValue(String iHandle, HandleValue iValue)
    throws FileNotFoundException, IOException, HandleException
    {
        HSAdapter api = HandleUtil.getHandleApi();
        
        HandleValue[] values = new HandleValue[] { iValue };
        
        HandleUtil.updateHandleValue(iHandle, values, api);
    }
    
    /**
     * Updates a single <CODE>HandleValue</CODE>.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iValue The <CODE>HandleValue</CODE> to be used to replace the current HandleValue target.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle update.
     */
    public static void updateHandleValue(String iHandle, HandleValue iValue, HSAdapter iApi)
    throws HandleException
    {
        HandleValue[] values = new HandleValue[] { iValue };
        
        HandleUtil.updateHandleValue(iHandle, values, iApi);
    }
    
    /**
     * Updates multiple <CODE>HandleValue</CODE>'s.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iValues A set of <CODE>HandleValue</CODE>'s to be used to replace the current HandleValue targets.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle update.
     */
    public static void updateHandleValue(String iHandle, HandleValue[] iValues)
    throws FileNotFoundException, IOException, HandleException
    {
        HSAdapter api = HandleUtil.getHandleApi();
        
        HandleUtil.updateHandleValue(iHandle, iValues, api);
    }
    
    /**
     * Updates multiple <CODE>HandleValue</CODE>'s.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iValues A set of <CODE>HandleValue</CODE>'s to be used to replace the current HandleValue targets.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle update.
     */
    public static void updateHandleValue(String iHandle, HandleValue[] iValues, HSAdapter iApi)
    throws HandleException
    {
        iApi.updateHandleValues(iHandle, iValues);
    }
    
    /**
     * Add's a new <CODE>HandleValue</CODE> to an existing Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iValue The <CODE>HandleValue</CODE> to be added.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle value addition.
     */
    public static void addHandleValue(String iHandle, HandleValue iValue)
    throws FileNotFoundException, IOException, HandleException
    {
        HSAdapter api = HandleUtil.getHandleApi();
        
        HandleValue[] values = new HandleValue[] { iValue };
        
        HandleUtil.addHandleValue(iHandle, values, api);
    }
    
    /**
     * Add's multiple new <CODE>HandleValue</CODE>'s to an existing Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iValues The <CODE>HandleValue</CODE>'s to be added.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle value addition.
     */
    public static void addHandleValue(String iHandle, HandleValue[] iValues, HSAdapter iApi)
    throws HandleException
    {
        iApi.addHandleValues(iHandle, iValues);
    }
    
    /**
     * Resolves the specified Handle within the Handle System.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iRequestedValues A set of <CODE>HandleValue</CODE> types to be retreived during resolution.
     * @param iRequestedIndexes A set of requested HandleValue indeces to be retrieved during resolution.
     * @return Returns a set of <CODE>HandleValue</CODE>'s retrieved during resolution.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle resolution.
     */
    public static HandleValue[] resolveHandle(String iHandle, String[] iRequestedValues, int[] iRequestedIndexes)
    throws FileNotFoundException, IOException, HandleException
    {
        HSAdapter api = HandleUtil.getHandleApi();
        
        HandleValue[] values = HandleUtil.resolveHandle(iHandle, iRequestedValues, iRequestedIndexes, api);
        
        return values;
    }
    
    /**
     * Resolves the specified Handle within the Handle System.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iRequestedValues A set of <CODE>HandleValue</CODE> types to be retreived during resolution.
     * @param iRequestedIndexes A set of requested HandleValue indeces to be retrieved during resolution.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     * @return Returns a set of <CODE>HandleValue</CODE>'s retrieved during resolution.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle resolution.
     */
    public static HandleValue[] resolveHandle(String iHandle, String[] iRequestedValues, int[] iRequestedIndexes, HSAdapter iApi)
    throws HandleException
    {
        HandleValue[] values = iApi.resolveHandle(iHandle, null, null);
        
        return values;
    }
    
    /**
     * Gets the website service account private key file from the local file system.
     * @return Returns the private key file as a byte array.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     */
    public static byte[] getPrivateKeyFile()
    throws FileNotFoundException, IOException
    {
        byte[] fileBytes = null;
        
        String path = System.getenv("REGISTRY_WEBSITE_HOME");
        
        path += HandleUtil.getProperty("handle_admin_key_file");
        
        File privKeyFile = new File(path);
        
        if (privKeyFile.exists() == false)
        {
            throw new FileNotFoundException("The admin private key file could not be found.");
        }
        
        if (privKeyFile.canRead() == false)
        {
            throw new IOException("The admin private key file cannot be read.");
        }
        
        RandomAccessFile privateKeyContents = new RandomAccessFile(privKeyFile, "r");
        
        if(privateKeyContents != null)
        {
            int length = (int) privateKeyContents.length();
            
            if(length > 0)
            {
                fileBytes = new byte[length];
                privateKeyContents.read(fileBytes);
            }
            else
            {
                throw new IOException("The private key file is empty.");
            }
        }
        else
        {
            throw new IOException("The private key file could not be opened.");
        }
        
        return fileBytes;
    }
    
    /**
     * Gets the password for the website Handle System service account.
     * @return Returns the password for the website Handle System service account as byte array.
     */
    public static byte[] getAdminUserCypherAsByteArray()
    {
        String cypher = "";
        
        try
        {
            cypher = HandleUtil.getProperty("handle_admin_password");
        }
        catch (IOException ex)
        {
            //Do nothing. Password will assumed to be an empty string.
        }
        
        return cypher.getBytes();
    }
    
    /**
     * Gets the website Handle System service account Handle name.
     * @return Returns the website Handle System service account as a <CODE>String</CODE>.
     */
    public static String getAdminUserHandleAsString()
    {
        String admin = "";
        
        try
        {
            admin = HandleUtil.getProperty("admin_user_handle");
        }
        catch (IOException ex)
        {
            //Do nothing. Admin handle will assumed to be an empty string.
        }
        
        return admin;
    }
    
    /**
     * Gets the index of the website Handle System service account Handle.
     * @return Returns the index of the website Handle System service account Handle as an <CODE>int</CODE>.
     */
    public static int getAdminUserAdminIndex()
    {
        int index = 200;
        
        try
        {
            index = Integer.parseInt(HandleUtil.getProperty("admin_user_handle_index"));
        }
        catch (NumberFormatException ex)
        {
            //Do nothing. Index will be assumed to be the handle system default admin index of 200.
        }
        catch (IOException ex)
        {
            //Do nothing. Index will be assumed to be the handle system default admin index of 200.
        }
        
        return index;
    }
    
    /**
     * Gets <CODE>HandleValue</CODE> containing the HS_SECKEY at index 301 of the specified user Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @return Returns the <CODE>HandleValue</CODE> containing the HS_SECKEY at index 301 of the specified user Handle.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle resolution.
     */
    public static HandleValue getSecretKey(String iHandle)
    throws FileNotFoundException, IOException, HandleException
    {
        HSAdapter api = HandleUtil.getHandleApi();
        
        HandleValue key = HandleUtil.getSecretKey(iHandle, api);
        
        return key;
    }
    
    /**
     * Gets <CODE>HandleValue</CODE> containing the HS_SECKEY at index 301 of the specified user Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     * @return Returns the <CODE>HandleValue</CODE> containing the HS_SECKEY at index 301 of the specified user Handle.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle resolution.
     */
    public static HandleValue getSecretKey(String iHandle, HSAdapter iApi)
    throws HandleException
    {
        HandleValue key = null;
        
        HandleValue[] values = HandleUtil.resolveHandle(iHandle, null, null, iApi);
        
        for (int i = 0; i < values.length; i++)
        {
            HandleValue value = values[i];
            
            if (value.getIndex() == HandleUtil.CONTRIBUTOR_PRIVATE_KEY_INDEX)
            {
                key = value;
                
                break;
            }
        }
        
        return key;
    }
    
    /**
     * Updates the <CODE>HandleValue</CODE> containing the HS_SECKEY at index 301 of the specified user Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iNewPassword The <CODE>String</CODE> value to set as the new password.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle update.
     * @throws java.lang.Exception Throws <CODE>Exception</CODE> if an error occurs while encrypting the new password value using SHA encryption.
     */
    public static void updateSecretKey(String iHandle, String iNewPassword)
    throws FileNotFoundException, IOException, HandleException, Exception
    {
        HSAdapter api = HandleUtil.getHandleApi();
        
        HandleUtil.updateSecretKey(iHandle, iNewPassword, api);
    }
    
    /**
     * Updates the <CODE>HandleValue</CODE> containing the HS_SECKEY at index 301 of the specified user Handle.
     * @param iHandle A <CODE>String</CODE> Handle name of the Handle to be acted upon.
     * @param iNewPassword The <CODE>String</CODE> value to set as the new password.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle update.
     * @throws java.lang.Exception Throws <CODE>Exception</CODE> if an error occurs while encrypting the new password value using SHA encryption.
     */
    public static void updateSecretKey(String iHandle, String iNewPassword, HSAdapter iApi)
    throws HandleException, Exception
    {
        HandleValue[] values = HandleUtil.resolveHandle(iHandle, null, null, iApi);
        
        for (int i = 0; i < values.length; i++)
        {
            HandleValue value = values[i];
            
            if (value.getIndex() == 301)
            {
                byte[] encodedPassword = Encoder.encodeSecretKey(iNewPassword.getBytes(), true);
                
                HandleValue newSecKey = new HandleValue(HandleUtil.CONTRIBUTOR_PRIVATE_KEY_INDEX, Common.STD_TYPE_HSSECKEY, encodedPassword);
                newSecKey.setAnyoneCanRead(false);
                newSecKey.setAnyoneCanWrite(false);
                
                HandleUtil.updateHandleValue(iHandle, newSecKey, iApi);
            }
        }
    }
    
    /**
     * Gets a new <CODE>HSAdapter</CODE> Handle System api object.
     * @return Returns a new <CODE>HSAdapter</CODE> Handle System api object.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if a new <CODE>HSAdapter</CODE> Handle System api object cannot be instantiated.
     */
    public static HSAdapter getHandleApi()
    throws FileNotFoundException, IOException, HandleException
    {
        byte[] privateKeyFile = HandleUtil.getPrivateKeyFile();
        String adminHandle = HandleUtil.getAdminUserHandleAsString();
        int adminIndex = HandleUtil.getAdminUserAdminIndex();
        byte[] cypher = HandleUtil.getAdminUserCypherAsByteArray();
        
        HSAdapter api = HSAdapterFactory.newInstance(adminHandle, adminIndex, privateKeyFile, cypher);
        
        return api;
    }
    
    /**
     * Gets a property from the Handle library configuration file.
     * @param iPropertyName The <CODE>String</CODE> name of the property to be retrieved from the Handle library configuration file.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the Handle configuration file cannot be found.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the Handle configuration file cannot be read.
     * @return Returns the <CODE>String</CODE> value of the specified property.
     */
    private static String getProperty(String iPropertyName)
    throws FileNotFoundException, IOException
    {
        String value = "";
        
        String configFilePath = System.getenv("PORTAL_HOME");
        
        Properties properties = new Properties();
        
        String path = configFilePath + "handle.properties";
        
        FileInputStream fis = new FileInputStream(path);
        
        properties.load(fis);
        
        value = properties.getProperty(iPropertyName);
        
        fis.close();
        
        return value;
    }
}
